package morenoapp.components.thinktable;

import java.awt.Color;
import java.util.Arrays;
import java.util.List;
import javax.swing.event.TableModelEvent;
import morenoapp.components.common.table.column.ColumnMeta;
import morenoapp.components.common.AbstractMorenoTableModel;
import morenoapp.components.common.CustomColumnsTableModel;
import morenoapp.components.common.MorenoFilteredTableModel;
import morenoapp.components.common.QuestionsFilter;
import morenoapp.components.common.table.column.AbstractColumnMeta;
import morenoapp.components.common.table.renderer.StyledTableValue;
import morenoapp.model.enums.MorenoQuestionType;
import morenoapp.model.data.MorenoAnswerFace;
import morenoapp.model.data.MorenoAnswerItemFace;
import morenoapp.model.data.MorenoMeasureFace;
import morenoapp.model.data.MorenoMetricFilterFace;
import morenoapp.model.data.MorenoQuestionFace;
import morenoapp.model.data.MorenoQuestionItemFace;
import morenoapp.model.data.MorenoRespondentFace;
import morenoapp.model.impl.MorenoMeasurePlanar;
import morenoapp.model.listeners.MorenoModelAdapter;
import morenoapp.model.listeners.MorenoModelListener;
import morenoapp.util.I18N;
import morenoapp.util.MorenoUtil;

/**
 *
 * @author dmitry.mamonov
 */
public class ThinktableTableModel extends AbstractMorenoTableModel implements MorenoFilteredTableModel, CustomColumnsTableModel {
    private static MorenoMeasureFace measureWeighed =
            new MorenoMeasureFace() {
                public double getWeight(int position) {
                    if (position < 0) {
                        throw new RuntimeException("Position must be >=1"); //NOI18N
                    } else if (position == 1) {
                        return 3.0;
                    } else if (position == 2) {
                        return 2.0;
                    } else if (position == 3) {
                        return 1.0;
                    } else {
                        return 0.5;
                    }
                }
            };
    private StyledTableValue[][] values = null;
    private QuestionsFilter defaultQuestionsFilter = new QuestionsFilter() {
        public List<MorenoQuestionFace> getFilteredQuestionList() {
            return getMorenoModel().getQuestionsList();
        }
    };
    private QuestionsFilter questionsFilter = defaultQuestionsFilter;

    public ThinktableTableModel() {
        super();

        //attach listeners
        MorenoModelListener modelListener = new MorenoModelAdapter() {
            @Override
            public void onRespondentsListChanged() {
                values = null;
                fireTableChanged(new TableModelEvent(ThinktableTableModel.this, TableModelEvent.HEADER_ROW));
                fireTableChanged(new TableModelEvent(ThinktableTableModel.this));
            }

            @Override
            public void onRespondentChanged(MorenoRespondentFace changed) {
                values = null;
                fireTableChanged(new TableModelEvent(ThinktableTableModel.this));
            }
        };
        getMorenoModel().addModelListener(modelListener);
    }

    public QuestionsFilter getQuestionsFilter() {
        return questionsFilter != null ? questionsFilter : defaultQuestionsFilter;
    }

    public void setQuestionsFilter(QuestionsFilter questionsFilter) {
        this.questionsFilter = questionsFilter;
    }

    public void fireFilterChanged() {
        values = null; //reset data set
        super.fireTableChanged(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
        super.fireTableChanged(new TableModelEvent(this));
    }

    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        throw new UnsupportedOperationException("Editing is not supported in this table."); //NOI18N
    }

//    protected MorenoMetricFilterFace getMetricFilter() {
//        return MorenoMetricFilterImpl.getFilterByQuestions(getQuestionsFilter().getFilteredQuestionList());
//    }
    /**
     * Исходная таблица разбита на несколько подтаблиц-секций,
     * каждая из которых имеет собственный алгоритм вычисления.
     *
     * Секции:
     * <table border='1'>
     *  <tr>
     *   <td>RespondentInfo - данные о респонденте</td>
     *   <td colspan='2'>RespondentData - выборы респондента</td>
     *   <td>RespondentSummary - итоги по выборам респодента</td>
     *  </tr>
     * <tr>
     *   <td>QuestionInfo - описание типа итогов по вопросу</td>
     *   <td colspan='2'>QuestionSummary - итоги по вопросу</td>
     *   <td>RespondentSummaryTorals - общие итоги по выборам респондентов</td>
     *  </tr>
     * <tr>
     *   <td>ModelSummaryInfo - описание типа итогов по модели в целом</td>
     *   <td>ModelSummary - значение итогов по модели в целом</td>
     *   <td>-</td>
     *   <td>-</td>
     *  </tr>
     * </table>
     *
     * @param rowIndex
     * @param columnIndex
     * @return
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (values == null) {
            List<MorenoRespondentFace> respondentsList = getMorenoModel().getRespondentsList();
            List<MorenoQuestionFace> questionsList = getQuestionsFilter().getFilteredQuestionList();

            values = new StyledTableValue[getRowCount()][];
            final int columnCount = getColumnCount();

            int row = 0;
            for (MorenoRespondentFace resp : respondentsList) {
                values[row] = new StyledTableValue[columnCount];
                StyledTableValue[] rowValues = values[row];
                int column = 0;
                {//информация о респонденте
                    rowValues[column] = getCellFromRespondentInfoSection(resp, column);
                    column++;
                    rowValues[column] = getCellFromRespondentInfoSection(resp, column);
                    column++;
                }
                {//выборы респондента
                    for (MorenoRespondentFace target : respondentsList) {
                        rowValues[column] = getCellFromRespondentDataSection(resp, target, questionsList);
                        column++;
                    }
                }
                {//итоги выборов респондента
                    for (MorenoQuestionFace quest : questionsList) {
                        rowValues[column] = getCellFromRespondentSummarySection(resp, quest, MorenoMeasurePlanar.getInstance());
                        column++;

                        rowValues[column] = getCellFromRespondentSummarySection(resp, quest, measureWeighed);
                        column++;
                    }
                }
                //переход к следующей строке
                row++;
            }
            //итоги выбора респондетна другими респондентами
            {
                int diagonalOffset = 0;
                for (MorenoQuestionFace quest : questionsList) {
                    values[row] = new StyledTableValue[columnCount];
                    values[row + 1] = new StyledTableValue[columnCount];
                    StyledTableValue[] rowZeroValues = values[row];
                    StyledTableValue[] rowOneValues = values[row + 1];

                    rowZeroValues[1] = getCellFromQuestionInfoSection(quest, MorenoMeasurePlanar.getInstance());
                    rowOneValues[1] = getCellFromQuestionInfoSection(quest, measureWeighed);

                    int column = 2;
                    for (MorenoRespondentFace resp : respondentsList) {
                        rowZeroValues[column] = getCellFromQuestionSummarySection(quest, resp, MorenoMeasurePlanar.getInstance());
                        rowOneValues[column] = getCellFromQuestionSummarySection(quest, resp, measureWeighed);
                        //переход к слудующему столбцу
                        column++;
                    }

                    {//Заполнение диагональных итоговых значиений по исследованию в целом
                        rowZeroValues[column + diagonalOffset + 0] = getCellFromQuestionSummaryTotalsSection(quest, respondentsList, MorenoMeasurePlanar.getInstance());
                        rowOneValues[column + diagonalOffset + 1] = getCellFromQuestionSummaryTotalsSection(quest, respondentsList, measureWeighed);
                        diagonalOffset += 2; //сдвиг диагонали
                    }


                    //переход к следующей строке
                    row += 2; //обработка по две строки
                }

                //взаимные выборы
                for (MorenoQuestionFace quest : questionsList) {
                    values[row] = new StyledTableValue[columnCount];
                    StyledTableValue[] rowValues = values[row];

                    rowValues[1] = getCellFromMutualInfoSection(quest);
                    int column = 2;
                    for (MorenoRespondentFace resp : respondentsList) {
                        rowValues[column] = getCellFromMutualSummarySection(quest, resp);

                        //переход к следующему столбцу
                        column++;
                    }
                    rowValues[column] = getCellFromMutualSummaryTotalSection(quest, respondentsList);

                    //переход к следующей строке
                    row++;
                }
            }

            //индекс - индивидуальный статус члена группы
            {
                values[row] = new StyledTableValue[columnCount];
                StyledTableValue[] rowValues = values[row];

                rowValues[1] = getCellFromIndexIndividualStatusInfoSection();
                int column = 2;
                for (MorenoRespondentFace resp : respondentsList) {
                    rowValues[column] = getCellFromIndexIndividualStatusValueSection(resp, respondentsList, questionsList);

                    //переход к следующему столбцу
                    column++;
                }

                //переход к следующей строке
                row++;
            }

            //Индекс - интегрированности группы
            {
                values[row] = new StyledTableValue[columnCount];
                StyledTableValue[] rowValues = values[row];
                rowValues[1] = getCellFromIndexGroupIntegrationInfoSection();
                if (columnCount>2){
                    //if not, model is empty, nothing to display, but 2 is minimum
                    rowValues[2] = getCellFromIndexGroupIntegrationValueSection(respondentsList, questionsList);
                }

                //переход к следующей строке
                row++;
            }

            //Индекс - сплоченности группы
            {
                values[row] = new StyledTableValue[columnCount];
                StyledTableValue[] rowValues = values[row];
                rowValues[1] = getCellFromIndexGroupSolidarityInfoSection();
                if (columnCount>2){
                    //if not, model is empty, nothing to display, but 2 is minimum
                    rowValues[2] = getCellFromIndexGroupSolidarityValueSection(respondentsList, questionsList);
                }

                //переход к следующей строке
                row++;
            }
        }

        StyledTableValue result = null;
        if (values.length>rowIndex){
            if (values[rowIndex].length>columnIndex){
                result = values[rowIndex][columnIndex];
            }
        }
        return result != null ? result : StyledTableValue.empty();
    }

    //получение данных по секциям
    /**
     * Данные о респонденте.
     * @param resp
     * @param column
     * @return
     */
    protected static StyledTableValue getCellFromRespondentInfoSection(MorenoRespondentFace resp, int column) {
        switch (column) {
            case 0: {
                return new StyledTableValue(
                        String.valueOf(resp.getIndex() + 1),
                        Color.black,
                        StyledTableValue.Aligment.Right,
                        null,
                        null);
            }
            case 1: {
                return new StyledTableValue(
                        resp.getName(),
                        resp.getSex().getColor(),
                        StyledTableValue.Aligment.Left,
                        null,
                        null);
            }
            default:
                throw new UnsupportedOperationException("unknown column index: " + column); //NOI18N
        }
    }

    /**
     * Выборы сделанные респондентом.
     * @param resp
     * @param target
     * @param questionsList 
     * @return
     */
    protected static StyledTableValue getCellFromRespondentDataSection(MorenoRespondentFace resp, MorenoRespondentFace target, List<MorenoQuestionFace> questionsList) {
        StringBuilder selection = new StringBuilder();
        for (MorenoQuestionFace question : questionsList) {
            MorenoAnswerFace respondentAnswer = resp.getAnswerByQuestion(question);
            MorenoAnswerItemFace answerItem = respondentAnswer.getAnswerItemByRespondent(target);
            if (answerItem != null) {
                MorenoQuestionItemFace questionItem = answerItem.getQuestionItem();
                if (questionItem != null) {
                    if (selection.length() > 0) {
                        selection.append(", "); //NOI18N
                    }
                    selection.append("<b>" + MorenoUtil.wrapTextWithColor(question.getColor(), question.getMarkerForQuestionItem(questionItem)) + "</b>"); //NOI18N
                }
            }
        }
        return new StyledTableValue(
                "<html>" + selection.toString(), //NOI18N
                Color.black,
                StyledTableValue.Aligment.Left,
                null,
                null);

    }

    /**
     * Итоги по выборам респондентов.
     * @param resp
     * @param quest
     * @param measure
     * @return
     */
    protected static StyledTableValue getCellFromRespondentSummarySection(MorenoRespondentFace resp, MorenoQuestionFace quest, MorenoMeasureFace measure) {
        MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
        double value = resp.getMetricCountOfGivenPreferences(filter, measure);
        String textValue = String.format("%.2f", value); //NOI18N

        return new StyledTableValue(
                textValue,
                quest.getColor(),
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Наименования итогов по выборам респондента другими респондентами.
     * @param quest
     * @param measure
     * @return
     */
    protected static StyledTableValue getCellFromQuestionInfoSection(MorenoQuestionFace quest, MorenoMeasureFace measure) {
        return new StyledTableValue(quest.getType().getShortTitle() +
                (measure == MorenoMeasurePlanar.getInstance() ? "" : " - взвешенная"), quest.getColor(), StyledTableValue.Aligment.Left, null, null);
    }

    /**
     * Итоги по выборам респондента другими респондентами.
     * @param quest
     * @param resp
     * @param measure
     * @return
     */
    protected static StyledTableValue getCellFromQuestionSummarySection(MorenoQuestionFace quest, MorenoRespondentFace resp, MorenoMeasureFace measure) {
        MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
        double value = resp.getMetricCountOfReceivedPreferences(filter, measure);
        String textValue = String.format("%.2f", value); //NOI18N

        return new StyledTableValue(
                textValue,
                quest.getColor(),
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    protected static StyledTableValue getCellFromQuestionSummaryTotalsSection(MorenoQuestionFace quest, List<MorenoRespondentFace> respondentsList, MorenoMeasureFace measure) {
        MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
        double value = 0.0;
        for (MorenoRespondentFace resp : respondentsList) {
            value += resp.getMetricCountOfReceivedPreferences(filter, measure);
        }
        String textValue = String.format("%.2f", value); //NOI18N

        return new StyledTableValue(
                textValue,
                quest.getColor(),
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Наименования итогов по взаимным выборам респондентов.
     * @param quest
     * @return
     */
    protected static StyledTableValue getCellFromMutualInfoSection(MorenoQuestionFace quest) {
        return new StyledTableValue(quest.getType().getShortTitle() +
                " - взаимный", quest.getColor(), StyledTableValue.Aligment.Left, null, null);
    }

    /**
     * Итоги взаимных выборов респондентов.
     * @param quest
     * @param resp
     * @return
     */
    protected static StyledTableValue getCellFromMutualSummarySection(MorenoQuestionFace quest, MorenoRespondentFace resp) {
        MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
        double value = resp.getMetricCountOfMutualPreference(filter);
        String textValue = String.format("%.0f", value); //NOI18N

        return new StyledTableValue(
                textValue,
                quest.getColor(),
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Итоги по выборам респондента другими респондентами.
     * @param quest
     * @param respondentsList 
     * @return
     */
    protected static StyledTableValue getCellFromMutualSummaryTotalSection(MorenoQuestionFace quest, List<MorenoRespondentFace> respondentsList) {
        MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
        double value = 0.0;
        for (MorenoRespondentFace resp : respondentsList) {
            value += resp.getMetricCountOfMutualPreference(filter);
        }
        String textValue = String.format("%.0f", value); //NOI18N

        return new StyledTableValue(
                textValue,
                quest.getColor(),
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Наименования итогов по взаимным выборам респондентов.
     * @return
     */
    protected static StyledTableValue getCellFromIndexIndividualStatusInfoSection() {
        return new StyledTableValue("Индив. статус", Color.black, StyledTableValue.Aligment.Left, null, null);
    }

    /**
     * Итоги взаимных выборов респондентов.
     * @param resp
     * @param respondentsList
     * @param questuionsList 
     * @return
     */
    protected static StyledTableValue getCellFromIndexIndividualStatusValueSection(MorenoRespondentFace resp, List<MorenoRespondentFace> respondentsList, List<MorenoQuestionFace> questuionsList) {
        double value = 0.0;
        for (MorenoQuestionFace quest : questuionsList) {
            if (quest.getType() == MorenoQuestionType.Positive || quest.getType() == MorenoQuestionType.Negative) {
                MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
                value += resp.getMetricCountOfReceivedPreferences(filter, MorenoMeasurePlanar.getInstance());
            }
        }
        int respondentsSubCount = respondentsList.size() - 1;
        String textValue = "NaN"; //NOI18N
        if (respondentsSubCount > 0) {
            value /= respondentsSubCount;
            textValue = String.format("%.2f", value); //NOI18N
        }

        return new StyledTableValue(
                textValue,
                Color.black,
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Наименования итогов по взаимным выборам респондентов.
     * @return
     */
    protected static StyledTableValue getCellFromIndexGroupIntegrationInfoSection() {
        return new StyledTableValue("Инд. интегрированности группы", Color.black, StyledTableValue.Aligment.Left, null, null);
    }

    /**
     * Итоги взаимных выборов респондентов.
     * @param respondentsList
     * @param questuionsList
     * @return
     */
    protected static StyledTableValue getCellFromIndexGroupIntegrationValueSection(List<MorenoRespondentFace> respondentsList, final List<MorenoQuestionFace> questuionsList) {
        MorenoMetricFilterFace positiveNegativeFilter = new MorenoMetricFilterFace() {
            public boolean hasAnswer(MorenoAnswerFace _answer) {
                return questuionsList.indexOf(_answer.getQuestion()) >= 0;
            }

            public List getQuestions() {
                throw new UnsupportedOperationException("No need to implement"); //NOI18N
            }
        };
        double value = 0.0;
        for (MorenoRespondentFace resp : respondentsList) {
            if ((int) resp.getMetricCountOfReceivedPreferences(positiveNegativeFilter, MorenoMeasurePlanar.getInstance()) == 0) {
                value += 1.0;
            }
        }

        int respondentsCount = respondentsList.size();
        String textValue = "NaN"; //NOI18N
        if (respondentsCount > 0) {
            value = 1 - (value / respondentsCount);
            textValue = String.format("%.2f", value); //NOI18N
        }

        return new StyledTableValue(
                textValue,
                Color.black,
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    /**
     * Наименования итогов по взаимным выборам респондентов.
     * @return
     */
    protected static StyledTableValue getCellFromIndexGroupSolidarityInfoSection() {
        return new StyledTableValue("Инд. сплочённости группы", Color.black, StyledTableValue.Aligment.Left, null, null);
    }

    /**
     * Индекс слочённости.
     * @param respondentsList
     * @param questuionsList 
     * @return
     */
    protected static StyledTableValue getCellFromIndexGroupSolidarityValueSection(List<MorenoRespondentFace> respondentsList, List<MorenoQuestionFace> questuionsList) {
        double value = 0.0;
        for (MorenoQuestionFace quest : questuionsList) {
            MorenoMetricFilterFace filter = new SingleQuestionMetricFilter(quest);
            for (MorenoRespondentFace resp : respondentsList) {
                value += resp.getMetricCountOfMutualPreference(filter);
            }
        }
        int respondentsCount = respondentsList.size();
        String textValue = "NaN"; //NOI18N
        if (respondentsCount > 1) {
            value /= respondentsCount * (respondentsCount - 1);
            textValue = String.format("%.2f", value); //NOI18N
        }

        return new StyledTableValue(
                textValue,
                Color.black,
                StyledTableValue.Aligment.Right,
                null,
                null);
    }

    @Override
    public int getRowCount() {
        return getMorenoModel().getRespondentsList().size() //данные по респондентам
                + getQuestionsFilter().getFilteredQuestionList().size() * 3 //отоги по вопросам, плоские, взвешенные и взаимные выборы
                + 3; //индексы по исследованию
    }

    @Override
    public int getColumnCount() {
        return 2 + getMorenoModel().getRespondentsList().size() + getQuestionsFilter().getFilteredQuestionList().size() * 2;
    }

    @Override
    public String getColumnName(final int columnIndex) {
        return getColumnMeta(columnIndex).getName();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return StyledTableValue.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    public ColumnMeta getColumnMeta(int index) {
        List<MorenoRespondentFace> respondentsList = getMorenoModel().getRespondentsList();
        int respondentsSize = respondentsList.size();
        List<MorenoQuestionFace> questionsList = getQuestionsFilter().getFilteredQuestionList();
        int questionsSize = questionsList.size();

        int column = index;
        if (column == 0) {
            return new AbstractColumnMeta("№", StyledTableValue.class, 26) { //NOI18N
                @Override
                public boolean isResizable() {
                    return false;
                }

                @Override
                public int getMaximumWidth() {
                    return getPrefferedWidth();
                }

                @Override
                public int getMinimumWidth() {
                    return getPrefferedWidth();
                }

                public Object getRowValue(int row) {
                    throw new UnsupportedOperationException("No need to implement."); //NOI18N
                }
            };
        }

        column -= 1;
        if (column == 0) {
            String caption = I18N.get("label.respondent");
            return new AbstractColumnMeta(caption, StyledTableValue.class, 130) {
                @Override
                public int getMinimumWidth() {
                    return 40;
                }

                public Object getRowValue(int row) {
                    throw new UnsupportedOperationException("No need to implement"); //NOI18N
                }
            };
        }

        column -= 1;
        if (column < respondentsSize) {
            return new AbstractColumnMeta("<html><center>" + (column + 1) + "</center>", StyledTableValue.class, 50) {  //NOI18N
                @Override
                public int getMinimumWidth() {
                    return 20;
                }

                public Object getRowValue(int row) {
                    throw new UnsupportedOperationException("No need to implement."); //NOI18N
                }
            };
        }

        column -= respondentsSize;
        if (column < questionsSize * 2) {
            int questionIndex = column / 2;
            int functionType = column % 2;
            final MorenoQuestionFace quest = questionsList.get(questionIndex);
            final String caption = String.format("<html><font color=\"%s\">", MorenoUtil.colorToHtml(quest.getColor())) + I18N.get("formula." + quest.getType().name() + ".given." + (functionType == 0 ? "plain" : "weighed"));
            return new AbstractColumnMeta(caption, StyledTableValue.class, 50) {
                @Override
                public int getMinimumWidth() {
                    return 80;
                }

                public Object getRowValue(int row) {
                    throw new UnsupportedOperationException("No need to implement."); //NOI18N
                }

                @Override
                public String getTooltip() {
                    return "<html>"+quest.toHtmlString(); //NOI18N
                }
            };
        }

        throw new IllegalArgumentException("Unknown column index: " + index); //NOI18N
    }

    protected static class SingleQuestionMetricFilter implements MorenoMetricFilterFace {
        MorenoQuestionFace question;

        public SingleQuestionMetricFilter(MorenoQuestionFace question) {
            this.question = question;
        }

        public boolean hasAnswer(MorenoAnswerFace _answer) {
            return _answer != null && _answer.getQuestion() == question;
        }

        public List<MorenoQuestionFace> getQuestions() {
            return Arrays.asList(question);
        }
    }

    public static class ReadOnlyColumnMeta extends AbstractColumnMeta {
        private ReadOnlyColumnMeta(String _name, Class _type, int _prefferedWidth) {
            super(_name, _type, _prefferedWidth);
        }

        public Object getRowValue(int row) {
            throw new UnsupportedOperationException("No need to implement."); //NOI18N
        }
    }
}
